package cn.lbd.gateway.filter;

import cn.lbd.gateway.utils.JwtTokenUtils;
import cn.lbd.gateway.utils.ResultUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.HashSet;
import java.util.stream.Collectors;


/**
 * 全局过滤器做统一鉴权  即对请求中的token进行验证，通过才放行
 *
 * 统一鉴权要求接口地址必须满足一些规范：
 * 数据库的URL要求：同一个一级菜单下的所有菜单要求统一一个前缀，比如图表相关菜单统一echarts开头，resource_url为/echarts/**
 * 接口地址要求：相关controller统一在类级别配上一个前缀调用地址(@RequestMapping)，名字与数据库对应菜单url前缀保持一致，
 *            比如图表的Controller，接口地址为/echarts/**  对应数据库图表菜单url的 /echarts/**
 */
@Component
@Slf4j
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Autowired
    private StringRedisTemplate template;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("进入了全局过滤器");
        ServerHttpRequest request = exchange.getRequest();
        try {
            //判断当前的请求是否为登录请求,如果是,则直接放行
            if (request.getURI().getPath().contains("/auth/login")) {
                return chain.filter(exchange);
            }
            //获取令牌进行验证
            HttpHeaders headers = request.getHeaders();
            String token = headers.getFirst("Authorization");
            String tokenHead = "Bearer ";
            if (token == null || "".equals(token)) {
                return ResultUtils.res(exchange, "token为空", 401);
            } else if (token.contains(tokenHead)) {
                token = token.substring(tokenHead.length());
            }
            //是否能从令牌中获取数据声明，获取不到代表token有问题（属于Token的合法性校验）
            Claims claims = JwtTokenUtils.getClaimsFromToken(token);
            if (claims == null) {
                return ResultUtils.res(exchange, "token不合法", 401);
            }
            String username = claims.getSubject();
            log.info("当前的登录用户：{}", username);
            if (username == null) {
                return ResultUtils.res(exchange, "token不合法", 401);
            }
            //检查token是否过期（属于Token的合法性校验），token过期时间应该和redis中的token过期时间保持一致
            if (JwtTokenUtils.isTokenExpired(token)) {
                return ResultUtils.res(exchange, "token过期", 401);
            }
            log.info("合法性校验通过");
            //token校验（比较是否与Redis中的token一致）
            String tokenKey = username + token + ":JWT";
            String redisToken = template.opsForValue().get(tokenKey);
            if (redisToken != null && redisToken.equals(token)) {
                log.info("token校验通过");
                //判断请求的URL是否有权限
                String requestPath = request.getURI().getPath();
                String authKey = username + ":Auth";
                String authorizeList = template.opsForValue().get(authKey);
                System.out.println("redis存储的路由权限"+authorizeList);
                String currentPath = "/" + requestPath.split("/")[1];
                System.out.println("当前路由"+currentPath);
                String[] split = authorizeList.substring(1, authorizeList.length() - 1).split(", ");
                HashSet<String> authorizeSet = Arrays.stream(split)
                        .collect(Collectors.toCollection(HashSet::new));
                if (requestPath != null && authorizeSet.contains(currentPath)) {
                    log.info("权限校验通过");
                    return chain.filter(exchange);
                }else {
                    return ResultUtils.res(exchange, "您没有访问权限", 403);
                }
            } else {
                return ResultUtils.res(exchange, "请勿伪造token", 401);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return ResultUtils.res(exchange);
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
